/* Copyright 2013 (C) FatalEror / Dean Bahari
 * 
 * jroCK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * See LICENSE file in main folder for more information.
 */

package jrock.other;

import static org.lwjgl.opengl.GL11.*;

import java.nio.FloatBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.util.vector.Vector3f;
import org.lwjgl.util.vector.Matrix4f;

/**
 * @author FatalEror
 */
public class Camera {
    private float       pitch     = 0.0f;
    private float       yaw       = 0.0f;
    private Vector3f    pos       = new Vector3f(0.0f, 0.0f, 0.0f);
    private FloatBuffer transform = BufferUtils.createFloatBuffer(16);
    
    // For frustum culling
    private class Plane {
        private Vector3f point  = new Vector3f();
        private Vector3f normal = new Vector3f();
        private float    D      = 0.0f;
        
        private void set(Vector3f pt, Vector3f n) {
            point.set(pt);
            normal.set(n);
            
            normal.negate();
            D = Vector3f.dot(normal, point);
            normal.negate();
        }
        
        private float distance(Vector3f pt) {
            return Vector3f.dot(normal, pt) + D;
        }
    }
    private float   zNear  = 0.01f;
    private float   zFar   = 1.0f;
    private float   height = (float)Math.tan(Math.PI / 4) * zNear;
    private float   width  = height * (800.0f / 600.0f);
    private Plane[] plane  = new Plane[6];
    
    public Camera() {
        for (int i = 0; i < 6; i++) {
            plane[i] = new Plane();
        }
    }
    
    public Camera(int x, int y, int z) {
        pos.x = x;
        pos.y = y;
        pos.z = z;
        for (int i = 0; i < 6; i++) {
            plane[i] = new Plane();
        }
    }
    
    public void pitch(float val) {
        pitch  += val;
    }
    
    public float pitch() {
        return pitch;
    }
    
    public void yaw(float val) {
        yaw    += val;
    }
    
    public float yaw() {
        return yaw;
    }
    
    public void walkBackward(float val) {
        pos.x -= val * Math.sin(Math.toRadians(yaw));
        pos.y += val * Math.sin(Math.toRadians(pitch));
        pos.z += val * Math.cos(Math.toRadians(yaw));
    }
    
    public void walkForward(float val) {
        pos.x += val * Math.sin(Math.toRadians(yaw));
        pos.y -= val * Math.sin(Math.toRadians(pitch));
        pos.z -= val * Math.cos(Math.toRadians(yaw));
    }
    
    public void move(float x, float y, float z) {
        pos.x += x;
        pos.y += y;
        pos.z += z;
    }
    
    public void strafeRight(float val) {
        pos.x += val * Math.cos(Math.toRadians(yaw));
        pos.z += val * Math.sin(Math.toRadians(yaw));
    }
    
    public void strafeLeft(float val) {
        pos.x -= val * Math.cos(Math.toRadians(yaw));
        pos.z -= val * Math.sin(Math.toRadians(yaw));
    }
    
    public void view() {
        glRotated(pitch, 1.0, 0.0, 0.0);
        glRotated(yaw, 0.0, 1.0, 0.0);
        glTranslated(-pos.x, -pos.y, -pos.z);
        glMultMatrix(transform);
    }
    
    public void setTransform(Matrix4f matrix) {
        matrix.store(transform);
        transform.flip();
    }
    
    public FloatBuffer getTransform() {
        return transform;
    }
    
    public Vector3f getPos() {
        return new Vector3f(pos);
    }
    
    public Vector3f getDirection() {
        Vector3f v = new Vector3f();
        v.x = (float)(Math.sin(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)));
        v.y = (float)-Math.sin(Math.toRadians(pitch));
        v.z = (float)(-Math.cos(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)));
        
        return v;
    }
    
    public void setCamDef(Vector3f u) {
        Vector3f z  = getDirection();
        Vector3f x  = new Vector3f();
        Vector3f y  = new Vector3f();
        Vector3f nc = new Vector3f(); 
        Vector3f fc = new Vector3f();
        
        Vector3f.cross(z, u, x);
        Vector3f.cross(x, z, y);
        
        x.normalise();
        y.normalise();
        
        Vector3f np = new Vector3f(z);
        Vector3f fp = new Vector3f(z);
        np.scale(zNear);
        fp.scale(zFar);
        
        Vector3f.add(np, pos, nc);
        Vector3f.add(fp, pos, fc);
        
        // NEAR PLANE
        plane[0].set(nc, z);
        
        // FAR PLANE
        z.negate();
        plane[1].set(fc, z);
        
        Vector3f pt     = new Vector3f();
        Vector3f aux    = new Vector3f();
        Vector3f normal = new Vector3f();
        
        // TOP PLANE
        pt.set(y);
        pt.scale(height);
        Vector3f.add(nc, pt, pt);
        Vector3f.sub(pt, pos, aux);
        aux.normalise();
        Vector3f.cross(aux, x, normal);
        plane[2].set(pt, normal);
        
        // BOTTOM PLANE
        pt.set(y);
        pt.scale(height);
        Vector3f.sub(nc, pt, pt);
        Vector3f.sub(pt, pos, aux);
        aux.normalise();
        Vector3f.cross(x, aux, normal);
        plane[3].set(pt, normal);
        
        // LEFT PLANE
        pt.set(x);
        pt.scale(width);
        Vector3f.sub(nc, pt, pt);
        Vector3f.sub(pt, pos, aux);
        aux.normalise();
        Vector3f.cross(aux, y, normal);
        plane[4].set(pt, normal);
        
        // RIGHT PLANE
        pt.set(x);
        pt.scale(width);
        Vector3f.add(nc, pt, pt);
        Vector3f.sub(pt, pos, aux);
        aux.normalise();
        Vector3f.cross(y, aux, normal);
        plane[5].set(pt, normal);
    }
    
    public boolean pointInFrustum(Vector3f pt) {
        for (int i = 0; i < 6; i++) {
            if (plane[i].distance(pt) < 0)
                return false;
        }
        
        return true;
    }
}
